package com.puzzlebazar.client.main.view;

import java.util.Iterator;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.puzzlebazar.client.puzzle.model.GenreFactory;
import com.puzzlebazar.client.puzzleinfo.NewPuzzleInfo;
import com.puzzlebazar.client.util.BoundInfo;
import com.puzzlebazar.client.util.ConcurrentVector;
import com.puzzlebazar.client.util.BasicFilter;
import com.puzzlebazar.client.util.NumberFilter;
import com.puzzlebazar.client.util.Vec2i;

public class NewPuzzleView extends Composite {

	private final TextBox nameTextBox = new TextBox();
	private final ListBox genreDropBox = new ListBox();
	private final HorizontalPanel sizePanel = new HorizontalPanel();
	private final TextBox sizeTextBox1 = new TextBox();
	private final TextBox sizeTextBox2 = new TextBox();
	private final Button okButton = new Button("OK");
	private final Button cancelButton = new Button("Cancel");
	private final String [] genres;
	
	private final ConcurrentVector<NewPuzzleCallback> callbacks = 
		new ConcurrentVector<NewPuzzleCallback>();

	/**
	 * Creates a composite widget that prompts the user for a new puzzle name
	 * and a genre.
	 */
	public NewPuzzleView() {

		final Grid grid = new Grid();
		grid.setWidth( "100%" );
		grid.setCellPadding( 7 );
		grid.setCellSpacing( 3 );
		grid.resize( 3, 2 );
		
		final Label nameLabel = new Label("Title:");
		nameLabel.setStylePrimaryName( "entryLabel" );
		final Label genreLabel = new Label("Genre:");
		genreLabel.setStylePrimaryName( "entryLabel" );
		final Label sizeLabel = new Label("Size:");
		sizeLabel.setStylePrimaryName( "entryLabel" );
		final HTML sizeLabelx = new HTML("&nbsp;&nbsp;x&nbsp;&nbsp;");

		nameTextBox.setWidth( "100%" );
		nameTextBox.addKeyPressHandler( new BasicFilter(new char[]{' '}) );
		nameTextBox.addKeyDownHandler( new KeyDownHandler() {
			@Override
			public void onKeyDown(KeyDownEvent event) {
				checkOkEnabled();
			}			
		});
		nameTextBox.addKeyUpHandler( new KeyUpHandler() {
			@Override
			public void onKeyUp(KeyUpEvent event) {
				checkOkEnabled();
			}
		});
		nameTextBox.addKeyPressHandler( new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				checkOkEnabled();
				if( event.getCharCode() == KeyCodes.KEY_ENTER &&
					okButton.isEnabled() )
					okButton.click();
			}
		});		
		
		genreDropBox.setWidth( "100%" );
		genres = GenreFactory.getInstance().getGenres();
		for (int i = 0; i < genres.length; i++) {
			genreDropBox.addItem(genres[i]);
		}
		genreDropBox.addChangeHandler( new ChangeHandler() {
			@Override
			public void onChange(ChangeEvent event) {
				ensureSizeIsValid();
			}

		} );
		

		sizeTextBox1.setWidth( "26px" );
		sizeTextBox1.setMaxLength(2);
		sizeTextBox2.setWidth( "26px" );
		sizeTextBox2.setMaxLength(2);
		sizeTextBox1.addKeyPressHandler( new NumberFilter() );
		sizeTextBox2.addKeyPressHandler( new NumberFilter() );
		sizeTextBox1.addKeyDownHandler( new KeyDownHandler() {
			@Override
			public void onKeyDown(KeyDownEvent event) {
				checkOkEnabled();
			}			
		});
		sizeTextBox1.addKeyUpHandler( new KeyUpHandler() {
			@Override
			public void onKeyUp(KeyUpEvent event) {
				checkOkEnabled();
			}
		});
		sizeTextBox1.addKeyPressHandler( new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				checkOkEnabled();
				if( event.getCharCode() == KeyCodes.KEY_ENTER &&
					okButton.isEnabled() )
					okButton.click();
			}
		});		
		sizeTextBox2.addKeyDownHandler( new KeyDownHandler() {
			@Override
			public void onKeyDown(KeyDownEvent event) {
				checkOkEnabled();
			}			
		});
		sizeTextBox2.addKeyUpHandler( new KeyUpHandler() {
			@Override
			public void onKeyUp(KeyUpEvent event) {
				checkOkEnabled();
			}
		});
		sizeTextBox2.addKeyPressHandler( new KeyPressHandler() {
			@Override
			public void onKeyPress(KeyPressEvent event) {
				checkOkEnabled();
				if( event.getCharCode() == KeyCodes.KEY_ENTER &&
					okButton.isEnabled() )
					okButton.click();
			}
		});
		
		sizePanel.add( sizeTextBox1 );
		sizePanel.add( sizeLabelx );
		sizePanel.add( sizeTextBox2 );

		grid.setWidget( 0, 0, nameLabel );
		grid.setWidget( 0, 1, nameTextBox );
		grid.setWidget( 1, 0, genreLabel );
		grid.setWidget( 1, 1, genreDropBox );
		grid.setWidget( 2, 0, sizeLabel );
		grid.setWidget( 2, 1, sizePanel );
		grid.getColumnFormatter().setWidth(0, "60px");
		
		// TODO Add genre-dependent fields based on the selection in the drop box 
		
		checkOkEnabled();
		okButton.setWidth( "100px" );		
		cancelButton.setWidth( "100px" );


		okButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				NewPuzzleInfo newPuzzleInfo = new NewPuzzleInfo();
				newPuzzleInfo.setTitle( nameTextBox.getText() );
				newPuzzleInfo.setGenre( genres[genreDropBox.getSelectedIndex()] );
				newPuzzleInfo.setSize( new Vec2i( Integer.parseInt(sizeTextBox1.getValue()),
						Integer.parseInt(sizeTextBox2.getValue()) ).toSizeString() );
				
				executeOk( newPuzzleInfo );
			}

		});	

		cancelButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				executeCancel();
			}
		});		
		
		final HorizontalPanel buttons = new HorizontalPanel();
		buttons.setWidth( "210px" );
		buttons.add(okButton);
		buttons.add(cancelButton);
		
		final VerticalPanel mainPanel = new VerticalPanel();
		mainPanel.add( grid );
		mainPanel.add( buttons );
		
		mainPanel.setStylePrimaryName( "mainContent" );
		mainPanel.setWidth("100%");
		initWidget(mainPanel);
		
	}

	/**
	 * Check that the size in the text box is within the bounds of the currently
	 * selected model, if not, load default size
	 */
	private void ensureSizeIsValid() {
		if( !isSizeIsValid() ) {
			String genre = genreDropBox.getValue( genreDropBox.getSelectedIndex() );
			Vec2i defaultSize = GenreFactory.getInstance().getFactory( genre ).getDefaultSize();
			sizeTextBox1.setValue( Integer.toString( defaultSize.x ) );
			sizeTextBox2.setValue( Integer.toString( defaultSize.y ) );
		}
	}

	/**
	 * @return True if the size in the text box is valid given the current puzzle bounds
	 */
	private boolean isSizeIsValid() {
		int x, y;
		String stringX = sizeTextBox1.getValue();
		String stringY = sizeTextBox2.getValue();
		
		
		if( stringX.length() == 0 || stringY.length() == 0 )
			return false;
		
		try {
			x = Integer.parseInt( stringX );
			y = Integer.parseInt( stringY );
		}
		catch( Exception e ) {
			return false;
		}
		
		String genre = genreDropBox.getValue( genreDropBox.getSelectedIndex() );
		BoundInfo boundInfo = GenreFactory.getInstance().getFactory( genre ).getBoundInfo();
		return boundInfo.isValidX(x) && boundInfo.isValidY(y);
	}

	/**
	 * Clears all the states of the dialog box so that it can be reused
	 */
	public void resetState() {
		nameTextBox.setText( "" );
		genreDropBox.setSelectedIndex(0);
		checkOkEnabled();
		ensureSizeIsValid();
		DeferredCommand.addCommand( new Command() {
			@Override
			public void execute() {
				nameTextBox.setFocus( true );
			}			
		});
	}

	/**
	 * Checks if the OK button should be enabled
	 */
	private void checkOkEnabled() {
		okButton.setEnabled( nameTextBox.getText().length() > 0  && isSizeIsValid() );
	}		

	/**
	 * Adds a callback to be executed when the user requests 
	 * the creation of a new puzzle or loads a puzzle
	 * 
	 * @param callback The callback to add
	 */
	public void addCallback( final NewPuzzleCallback callback ) {
		callbacks.add( callback );			
	}

	/**
	 * Remove a callback added with addCallback()
	 * 
	 * @param callback The callback to remove
	 */
	public void removeCallback( NewPuzzleCallback callback ) {
		callbacks.remove( callback );		
	}
	
	
	public interface NewPuzzleCallback {

		/**
		 * Called whenever the OK button is clicked
		 * 
		 * @param newPuzzleInfo The information on the new puzzle to create
		 */
		public void ok( NewPuzzleInfo newPuzzleInfo );
		
		/**
		 * Called whenever the cancel button is clicked
		 */
		public void cancel();
	}

	/**
	 * Execute all callbacks associated with the OK button 
	 *
	 * @param newPuzzleInfo The information on the new puzzle to create
	 */
	private void executeOk( NewPuzzleInfo newPuzzleInfo ) {
		
		for (Iterator<NewPuzzleCallback> iterator = callbacks.iterator(); iterator.hasNext();) {
			NewPuzzleCallback callback = iterator.next();
			callback.ok(newPuzzleInfo );			
		}		

	}

	/**
	 * Execute all callbacks associated with the Cancel button 
	 */
	private void executeCancel() {
		
		for (Iterator<NewPuzzleCallback> iterator = callbacks.iterator(); iterator.hasNext();) {
			NewPuzzleCallback callback = iterator.next();
			callback.cancel();			
		}		

	}
	
}
